#include <stdio.h>
#include <stdlib.h>
#include <stdint.h>
#include <SDL2/SDL.h>
#include <libavformat/avformat.h>
#include <libavcodec/avcodec.h>
#include <libswscale/swscale.h>
#include <libavutil/imgutils.h>

typedef struct VideoState {
    AVFormatContext *format_ctx;
    AVCodecContext *codec_ctx;
    int video_stream_index;
    struct SwsContext *sws_ctx_yuv_to_rgba;
    AVFrame *frame;
    AVFrame *frame_rgba;
    uint8_t *buffer;
    int num_bytes;
    SDL_Texture *texture;
    SDL_Renderer *renderer;
    SDL_mutex *mutex;
    SDL_cond *cond;
    int quit;
    int paused;
} VideoState;

int decode_thread(void *arg) {
    VideoState *vs = (VideoState *)arg;
    AVPacket packet;
    int response;

    while (av_read_frame(vs->format_ctx, &packet) >= 0) {
        if (packet.stream_index == vs->video_stream_index) {
            response = avcodec_send_packet(vs->codec_ctx, &packet);
            if (response < 0) {
                fprintf(stderr, "Failed to decode packet.\n");
                return -1;
            }

            while (response >= 0) {
                response = avcodec_receive_frame(vs->codec_ctx, vs->frame);
                if (response == AVERROR(EAGAIN) || response == AVERROR_EOF) {
                    break;
                } else if (response < 0) {
                    fprintf(stderr, "Failed to decode frame.\n");
                    return -1;
                }

                SDL_LockMutex(vs->mutex);
                while (vs->paused && !vs->quit) {
                    SDL_CondWait(vs->cond, vs->mutex);
                }
                if (vs->quit) {
                    SDL_UnlockMutex(vs->mutex);
                    break;
                }
                sws_scale(vs->sws_ctx_yuv_to_rgba, (const uint8_t *const *)vs->frame->data, vs->frame->linesize, 0, vs->codec_ctx->height, vs->frame_rgba->data, vs->frame_rgba->linesize);
                SDL_CondSignal(vs->cond);
                SDL_UnlockMutex(vs->mutex);
                SDL_Delay(40);  // Adjust delay to match the frame rate of your video
            }
        }
        av_packet_unref(&packet);

        SDL_LockMutex(vs->mutex);
        if (vs->quit) {
            SDL_UnlockMutex(vs->mutex);
            break;
        }
        SDL_UnlockMutex(vs->mutex);
    }

    SDL_LockMutex(vs->mutex);
    vs->quit = 1;
    SDL_CondSignal(vs->cond);
    SDL_UnlockMutex(vs->mutex);

    return 0;
}

int main(int argc, char *argv[]) {
    if (argc < 2) {
        fprintf(stderr, "Usage: %s <input file>\n", argv[0]);
        exit(1);
    }

    const char *input_filename = argv[1];

    // Initialize FFmpeg
    avformat_network_init();

    VideoState vs;
    vs.quit = 0;
    vs.paused = 0;

    vs.format_ctx = avformat_alloc_context();
    if (avformat_open_input(&vs.format_ctx, input_filename, NULL, NULL) != 0) {
        fprintf(stderr, "Could not open input file.\n");
        return -1;
    }

    if (avformat_find_stream_info(vs.format_ctx, NULL) < 0) {
        fprintf(stderr, "Could not find stream information.\n");
        return -1;
    }

    AVCodecParameters *codec_par = NULL;
    const AVCodec *codec = NULL;
    vs.video_stream_index = -1;

    for (int i = 0; i < vs.format_ctx->nb_streams; i++) {
        if (vs.format_ctx->streams[i]->codecpar->codec_type == AVMEDIA_TYPE_VIDEO) {
            vs.video_stream_index = i;
            codec_par = vs.format_ctx->streams[i]->codecpar;
            codec = avcodec_find_decoder(codec_par->codec_id);
            break;
        }
    }

    if (vs.video_stream_index == -1) {
        fprintf(stderr, "Could not find video stream.\n");
        return -1;
    }

    if (!codec) {
        fprintf(stderr, "Could not find decoder.\n");
        return -1;
    }

    vs.codec_ctx = avcodec_alloc_context3(codec);
    if (avcodec_parameters_to_context(vs.codec_ctx, codec_par) < 0) {
        fprintf(stderr, "Could not copy codec context.\n");
        return -1;
    }

    if (avcodec_open2(vs.codec_ctx, codec, NULL) < 0) {
        fprintf(stderr, "Could not open codec.\n");
        return -1;
    }

    // Initialize SDL
    if (SDL_Init(SDL_INIT_VIDEO) < 0) {
        fprintf(stderr, "Could not initialize SDL - %s\n", SDL_GetError());
        return -1;
    }

    SDL_Window *window = SDL_CreateWindow("Simple SDL Video Player", SDL_WINDOWPOS_UNDEFINED, SDL_WINDOWPOS_UNDEFINED, vs.codec_ctx->width, vs.codec_ctx->height, 0);
    if (!window) {
        fprintf(stderr, "Could not create window - %s\n", SDL_GetError());
        return -1;
    }

    vs.renderer = SDL_CreateRenderer(window, -1, SDL_RENDERER_ACCELERATED);
    vs.texture = SDL_CreateTexture(vs.renderer, SDL_PIXELFORMAT_RGBA32, SDL_TEXTUREACCESS_STREAMING, vs.codec_ctx->width, vs.codec_ctx->height);

    vs.sws_ctx_yuv_to_rgba = sws_getContext(vs.codec_ctx->width, vs.codec_ctx->height, vs.codec_ctx->pix_fmt,
                                            vs.codec_ctx->width, vs.codec_ctx->height, AV_PIX_FMT_RGBA,
                                            SWS_BILINEAR, NULL, NULL, NULL);

    vs.frame = av_frame_alloc();
    vs.frame_rgba = av_frame_alloc();
    vs.num_bytes = av_image_get_buffer_size(AV_PIX_FMT_RGBA, vs.codec_ctx->width, vs.codec_ctx->height, 1);
    vs.buffer = (uint8_t *)av_malloc(vs.num_bytes * sizeof(uint8_t));
    av_image_fill_arrays(vs.frame_rgba->data, vs.frame_rgba->linesize, vs.buffer, AV_PIX_FMT_RGBA, vs.codec_ctx->width, vs.codec_ctx->height, 1);

    vs.mutex = SDL_CreateMutex();
    vs.cond = SDL_CreateCond();

    SDL_Thread *decode_tid = SDL_CreateThread(decode_thread, "DecodeThread", &vs);

    while (!vs.quit) {
        SDL_Event event;
        while (SDL_PollEvent(&event)) {
            if (event.type == SDL_QUIT) {
                SDL_LockMutex(vs.mutex);
                vs.quit = 1;
                SDL_CondSignal(vs.cond);
                SDL_UnlockMutex(vs.mutex);
                break;
            } else if (event.type == SDL_KEYDOWN && event.key.keysym.sym == SDLK_SPACE) {
                SDL_LockMutex(vs.mutex);
                vs.paused = !vs.paused;
                SDL_CondSignal(vs.cond);
                SDL_UnlockMutex(vs.mutex);
            }
        }

        SDL_LockMutex(vs.mutex);
        if (!vs.paused && !vs.quit) {
            SDL_UpdateTexture(vs.texture, NULL, vs.frame_rgba->data[0], vs.frame_rgba->linesize[0]);
            SDL_RenderClear(vs.renderer);
            SDL_RenderCopy(vs.renderer, vs.texture, NULL, NULL);
            SDL_RenderPresent(vs.renderer);
            SDL_CondSignal(vs.cond);
        }
        SDL_UnlockMutex(vs.mutex);
    }

    SDL_WaitThread(decode_tid, NULL);

    av_frame_free(&vs.frame);
    av_frame_free(&vs.frame_rgba);
    av_free(vs.buffer);
    sws_freeContext(vs.sws_ctx_yuv_to_rgba);
    avcodec_free_context(&vs.codec_ctx);
    avformat_close_input(&vs.format_ctx);

    SDL_DestroyTexture(vs.texture);
    SDL_DestroyRenderer(vs.renderer);
    SDL_DestroyWindow(window);
    SDL_DestroyMutex(vs.mutex);
    SDL_DestroyCond(vs.cond);
    SDL_Quit();

    return 0;
}
